package com.fept.msgcore.msgserver.protocol.util;

import com.fept.msgcore.msgserver.protocol.exception.ProtocolErrorCodeEnum;
import com.fept.msgcore.msgserver.protocol.exception.ProtocolException;
import com.fept.msgcore.msgserver.protocol.frame.Frame;
import com.fept.msgcore.msgserver.protocol.model.base.KeyConstants;
import com.fept.msgcore.msgserver.protocol.model.base.ProtocolConstants;
import com.fept.msgcore.msgserver.protocol.model.body.BaseBody;
import com.fept.msgcore.msgserver.protocol.model.body.DataBody;
import com.fept.msgcore.msgserver.protocol.model.body.EventBody;
import com.fept.msgcore.msgserver.protocol.model.body.SimpleJsonValueBody;
import com.fept.msgcore.msgserver.protocol.model.message.MessageModel;
import com.fept.msgcore.msgserver.protocol.model.option.EncryptOption;
import com.fept.msgcore.msgserver.protocol.model.option.SignOption;
import com.fept.msgcore.msgserver.protocol.util.crypto.AESUtil;
import com.fept.msgcore.msgserver.protocol.util.crypto.DESUtil;
import com.fept.msgcore.msgserver.protocol.util.crypto.MACUtil;
import com.fept.msgcore.msgserver.protocol.util.crypto.MessageDigestUtil;
import com.fept.msgcore.msgserver.protocol.util.system.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

/**
 * Created by Srma on 2/9/2017.
 */
public class FrameHelper {

    private static Logger logger = LoggerFactory.getLogger(FrameHelper.class);

    /**
     *
     * @param msg
     * @param body
     * @return
     */
    public static MessageModel convertMessageBodyToMessageModel(Frame msg,
                                                                BaseBody body) {
        if (msg == null || msg.getHeader() == null || body == null
                || body.getType() == ProtocolConstants.BODY_NOP) {
            return null;
        }

        MessageModel messageModel = new MessageModel();
        messageModel.setIp(msg.getIp());
        messageModel.setPort(msg.getPort());
        messageModel.setSenderID(msg.getHeader().getSenderId() + "");
        messageModel.setType(body.getType() + "");

        switch (body.getType()) {
            case ProtocolConstants.BODY_COMMAND:
            case ProtocolConstants.BODY_SESSION:
            case ProtocolConstants.BODY_SETTING:
                SimpleJsonValueBody simpleJsonValueBody = (SimpleJsonValueBody) body;
                messageModel.setParams(simpleJsonValueBody.getJsonValue());
                break;
            case ProtocolConstants.BODY_EVENT:
                EventBody eventBody = (EventBody) body;
                messageModel.setSubType(eventBody.getEventType() + "");
                messageModel.setSource(eventBody.getEventSource() + "");
                messageModel.setParams(eventBody.getEventJson());
                break;

            case ProtocolConstants.BODY_DATA:
                DataBody dataBody = (DataBody) body;
                messageModel.setSubType(dataBody.getEventType() + "");
                messageModel.setSource(dataBody.getEventSource() + "");
                messageModel.setParams(dataBody.getEventJson());
                break;
            default:
                /**
                 * 默认情况，直接设置二进制值
                 */
                messageModel.setParams(ByteUtils.bytesToStringNoSpace(body
                        .getValue()));
                break;
        }

        return messageModel;
    }

    /**
     *
     * @param encryptOption
     */
    public static void checkEncryptKeyID(EncryptOption encryptOption) {
        switch (encryptOption.getType()) {
            case EncryptOption.AES:
            case EncryptOption.DES:
            case EncryptOption.DES3:
                if (encryptOption.getKey() >= KeyConstants.KEYSET.length) {
                    throw new ProtocolException(ProtocolErrorCodeEnum.INVALID_KEY);
                }
            default:
                return;
        }
    }

    /**
     *
     * @param signOption
     */
    public static void checkSignKeyID(SignOption signOption) {
        switch (signOption.getType()) {
            case SignOption.HMAC_MD5:
            case SignOption.HMAC_SHA1:
            case SignOption.MD5:
            case SignOption.SHA1:
                if (signOption.getKey() >= KeyConstants.KEYSET.length) {
                    throw new ProtocolException(ProtocolErrorCodeEnum.INVALID_KEY);
                }
            default:
                return;
        }
    }

    /**
     *
     * @param encryptOption
     * @param bodyData
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeySpecException
     */
    public static byte[] encryptData(EncryptOption encryptOption,
                                     byte[] bodyData) throws InvalidKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException,
            BadPaddingException, InvalidKeySpecException {
        if (encryptOption == null || bodyData == null) {
            return null;
        }

        int alg = encryptOption.getAlgorithm();
        int key = encryptOption.getKey();

        /**
         * 校验加密秘钥
         */
        checkEncryptKeyID(encryptOption);

        switch (alg) {
            case EncryptOption.NONE:
                /**
                 * 直接返回原始内容
                 */
                return bodyData;
            case EncryptOption.AES:
                return AESUtil.encrypt(KeyConstants.KEYSET[key], bodyData);
            case EncryptOption.DES:
                return DESUtil.encrypt(KeyConstants.KEYSET[key], bodyData);
            case EncryptOption.DES3:
                return DESUtil.encrypt3DES(KeyConstants.KEYSET[key], bodyData);
            default:
                logger.error(String.format("encrytion algrithm %X not supproted",
                        alg));
                throw new ProtocolException(
                        ProtocolErrorCodeEnum.UNSUPPORTED_ALGORITHM);
        }
    }

    /**
     *
     * @param encryptOption
     * @param bodyData
     * @return
     */
    public static byte[] decryptData(EncryptOption encryptOption,
                                     byte[] bodyData) throws Exception {
        if (encryptOption != null && bodyData != null) {
            int alg = encryptOption.getAlgorithm();
            int key = encryptOption.getKey();

            /**
             * 校验解密秘钥
             */
            checkEncryptKeyID(encryptOption);

            switch (alg) {
                case EncryptOption.NONE:
                    /**
                     * 直接返回原始内容
                     */
                    return bodyData;
                case EncryptOption.AES:

                    return AESUtil.decrypt(KeyConstants.KEYSET[key], bodyData);
                case EncryptOption.DES:
                    return DESUtil.decrypt(KeyConstants.KEYSET[key], bodyData);
                case EncryptOption.DES3:
                    return DESUtil.decrypt3DES(KeyConstants.KEYSET[key], bodyData);
                default:
                    logger.error(String.format(
                            "encrytion algrithm %X not supproted", alg));
                    throw new ProtocolException(
                            ProtocolErrorCodeEnum.UNSUPPORTED_ALGORITHM);
            }
        }
        return null;
    }

    /**
     *
     * @param sign
     * @param bodyData
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public static byte[] sign(SignOption sign, byte[] bodyData)
            throws NoSuchAlgorithmException, InvalidKeyException {
        int alg = sign.getAlgorithm();
        int key = sign.getKey();

        /**
         * 校验签名秘钥
         */
        checkSignKeyID(sign);

        switch (alg) {
            case SignOption.NONE:
                /**
                 * 空签名
                 */
                return new byte[0];
            case SignOption.MD5:
                return MessageDigestUtil.messageDigest(bodyData, "MD5");
            case SignOption.SHA1:
                return MessageDigestUtil.messageDigest(bodyData, "SHA1");
            case SignOption.HMAC_MD5:
                return MACUtil.hamc("HmacMD5", KeyConstants.KEYSET[key], bodyData);
            case SignOption.HMAC_SHA1:
                return MACUtil.hamc("HmacSHA1", KeyConstants.KEYSET[key], bodyData);
            default:
                logger.error(String.format("sign algrithm %X not supproted", alg));
                throw new ProtocolException(
                        ProtocolErrorCodeEnum.UNSUPPORTED_ALGORITHM);
        }
    }

    /**
     *
     * @param sign
     * @param bodyData
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public static boolean validateSign(SignOption sign, byte[] bodyData)
            throws NoSuchAlgorithmException, InvalidKeyException {
        if (sign != null && bodyData != null) {
            byte[] mac = sign.getMac();
            int alg = sign.getAlgorithm();
            int key = sign.getKey();

            /**
             * 校验验签秘钥
             */
            checkSignKeyID(sign);

            switch (alg) {
                case SignOption.NONE:
                    /**
                     * 空签名，直接通过
                     */
                    return true;
                case SignOption.MD5:
                    byte[] md5Mac = MessageDigestUtil
                            .messageDigest(bodyData, "MD5");
                    return ByteUtils.bytesEqual(mac, md5Mac);
                case SignOption.SHA1:
                    byte[] sha1Mac = MessageDigestUtil.messageDigest(bodyData,
                            "SHA1");
                    return ByteUtils.bytesEqual(mac, sha1Mac);
                case SignOption.HMAC_MD5:
                    byte[] hmacMD5 = MACUtil.hamc("HmacMD5",
                            KeyConstants.KEYSET[key], bodyData);
                    return ByteUtils.bytesEqual(mac, hmacMD5);
                case SignOption.HMAC_SHA1:
                    byte[] hmacSha1 = MACUtil.hamc("HmacSHA1",
                            KeyConstants.KEYSET[key], bodyData);
                    return ByteUtils.bytesEqual(mac, hmacSha1);
                default:
                    logger.error(String.format("sign algrithm %X not supproted",
                            alg));
                    throw new ProtocolException(
                            ProtocolErrorCodeEnum.UNSUPPORTED_ALGORITHM);
            }
        }
        return false;
    }

}
